package utils;
import com.alibaba.druid.util.StringUtils;
import com.esotericsoftware.reflectasm.MethodAccess;
import model.Query;

import java.lang.reflect.Field;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ModelUtils {

    private static final String GET_PREFIX = "get";
    private static final String SET_PREFIX = "set";
    private static final ConcurrentMap<Class<?>, MethodAccess> methodCache = new ConcurrentHashMap<>();
//    private static final ConcurrentMap<Class<?>, FieldAccess> fieldCache = new ConcurrentHashMap<>();
//    private static class FieldAccess {
//        private HashMap<String, Field> fields = new HashMap<>();
//        private FieldAccess() {}
//        public static FieldAccess get(Class<?> clazz) {
//            Field[] fieldArray = clazz.getDeclaredFields();
//            FieldAccess fieldAccess = new FieldAccess();
//            for (Field f : fieldArray) {
//                fieldAccess.fields.put(f.getName(), f);
//            }
//            return fieldAccess;
//        }
//        public Field[] getFields() {
//            return fields.values().toArray(new Field[1]);
//        }
//        public Field field(String name) {
//            return fields.get(name);
//        }
//    }

    public static MethodAccess getMethodAccess(Class<?> clazz) {
        if (methodCache.containsKey(clazz)) {
            return methodCache.get(clazz);
        }

        MethodAccess methodAccess = MethodAccess.get(clazz);
        methodCache.putIfAbsent(clazz, methodAccess);
        return methodAccess;
    }

//    public static FieldAccess getFieldAccess(Class<?> clazz) {
//        if (fieldCache.containsKey(clazz)) {
//            return fieldCache.get(clazz);
//        }
//
//        FieldAccess fieldAccess = FieldAccess.get(clazz);
//        fieldCache.putIfAbsent(clazz, fieldAccess);
//        return fieldAccess;
//    }

    public static void queryToModel(Query query, Object object) {
        if (null == object) { throw new NullPointerException("object不能为null"); }
        if (null == query.keys || null == query.values) { throw new IllegalArgumentException("query未初始化"); }
        try {
            String[] keys = query.keys;
            String[][] values = query.values;

            Class<?> clazz = object.getClass();
            MethodAccess methodAccess = getMethodAccess(clazz);
            for (int i = 0; i < keys.length; i++) {
                Field field = clazz.getDeclaredField(keys[i]);
                String simpleName = field.getType().getSimpleName();
                String methodName = SET_PREFIX + keys[i].substring(0, 1).toUpperCase() + keys[i].substring(1);
                switch (simpleName) {
                    case "String":
                        methodAccess.invoke(object, methodName, values[i][0]);
                        break;
                    case "Integer":
                        methodAccess.invoke(object, methodName, Integer.valueOf(values[i][0]));
                        break;
                    case "Double":
                        methodAccess.invoke(object, methodName, Double.valueOf(values[i][0]));
                        break;
                    case "String[]":
                        methodAccess.invoke(object, methodName, new Object[]{values[i]});
                        break;
                    default:
                        throw new UnsupportedOperationException("暂不支持" + simpleName + "类型的封装");
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void copyProperties(Object dst, Object src) {
        if (null == dst || null == src) { throw new NullPointerException("参数不能为null"); }
        copyProperties(dst, src, null, null);
    }

    public static void copyProperties(Object dst, Object src, List<String> fields) {
        if (null == dst || null == src || null == fields) { throw new NullPointerException("参数不能为null"); }
        copyProperties(dst, src, fields, null);
    }

    public static void copyProperties(Object dst, Object src, Map<String, String> map) {
        if (null == dst || null == src || null == map) { throw new NullPointerException("参数不能为null"); }
        copyProperties(dst, src, null, map);
    }

    /**
     * 对dst的成员变量赋值为对应的src成员变量的值，BeanUtils.copyProperties的增强版
     * @param dst 待赋值的Bean
     * @param src dst的数据来源Bean
     * @param srcf 指定用src中的哪些成员给dst赋值。可以是null，表示使用所有成员给dst赋值
     * @param map 制定dst中的成员到src中的成员的对应关系，用于dst和src中成员名称不一致的情况。可以是null，表示dst和src中的成员名称一一对应
     */
    public static void copyProperties(Object dst, Object src, List<String> srcf, Map<String, String> map) {
        Set<String> srcfields = null;
        if (null != srcf) { srcfields = new HashSet<>(srcf); }
        if (null == dst || null == src) { throw new NullPointerException("参数不能为null"); }
        Class<?> dstClass = dst.getClass();
        Class<?> srcClass = src.getClass();
        MethodAccess dstMethods = getMethodAccess(dstClass);
        MethodAccess srcMethods = getMethodAccess(srcClass);
 //       FieldAccess dstFields = getFieldAccess(dstClass);
//        FieldAccess srcFields = getFieldAccess(srcClass);

//        Field[] dstFieldArray = dstFields.getFields();
        Field[] dstFieldArray = dstClass.getDeclaredFields();
        try {
            for (Field fdst : dstFieldArray) {
                String field = fdst.getName();
                Field fsrc = null;

//                map==null   srcfields==null   srcfields.contains(field || mapExist -> map.get(field))     result
//                0           0                 0                                                           0
//                0           0                 1                                                           1
//                0           1                 0                                                           2
//                0           1                 1                                                           3
//                1           0                 0                                                           4
//                1           0                 1                                                           5
//                1           1                 0                                                           6
//                1           1                 1                                                           7
//                if (null == map && null == srcfields || srcfields.contains(field)) {
//                    fsrc = srcClass.getDeclaredField(field);
//                } else if (null == map) {
//                    continue;
//                } else if (srcfields.contains(field) || srcfields.contains(map.get(field))) {
//                    String srcName = map.get(field);
//                    if (null == srcName) {
//                        fsrc = srcClass.getDeclaredField(field);
//                    } else {
//                        fsrc = srcClass.getDeclaredField(srcName);
//                    }
//                } else { //map不为空 且 srcFields不为空 且 srcfields不包含field
//                    continue;
//                }

                //  map     srcfields   selector
                //  0       0           0
                //  0       1           1
                //  1       0           2
                //  1       1           3
                int selector = ((null == map ? 0 : 1) << 1) + (null == srcfields ? 0 : 1);
                fsrc = DISPATCHERS[selector].apply(srcClass, srcfields, map, field);
//                fsrc = DISPATCHERS[selector].apply(srcFields, srcfields, map, field);
                if (null == fsrc) { continue; }

                if (!fsrc.getType().equals(fdst.getType())) {
                    throw new IllegalArgumentException("src:" + fsrc.getType() + ", "
                            + "dst:" + fdst.getType() + ", " + field + "类型不同");
                }
                String dstfieldName = field.substring(0, 1).toUpperCase() + field.substring(1);
                String srcfieldName = fsrc.getName().substring(0, 1).toUpperCase() + fsrc.getName().substring(1);

                if (List.class.isAssignableFrom(fsrc.getType())) {
                    List<?> srcList = (List<?>) srcMethods.invoke(src, GET_PREFIX + srcfieldName);
                    List<Object> dstList = new ArrayList<>(srcList.size());
                    dstMethods.invoke(dst, SET_PREFIX + dstfieldName, dstList);
                    dstList.addAll(srcList);
                    return;
                }
                dstMethods.invoke(dst, SET_PREFIX + dstfieldName, srcMethods.invoke(src, GET_PREFIX + srcfieldName));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private interface Dispatcher {
        Field apply(Class<?> srcClass, Set<String> srcfields, Map<String, String> map, String field) throws NoSuchFieldException;
    }

    private static final Dispatcher mapNullSrcfieldsNull = (srcClass, srcfields, map, field) -> srcClass.getDeclaredField(field);

    private static final Dispatcher mapNullSrcfieldsNotNull = (srcClass, srcfields, map, field) -> {
        if (srcfields.contains(field)) {
            return srcClass.getDeclaredField(field);
        }
        return null;
    };

    private static final Dispatcher mapNotNullSrcfieldsNull = (srcClass, srcfields, map, field) -> {
        String srcName = map.get(field);
        if (null == srcName) {
            return srcClass.getDeclaredField(field);
        } else {
            return srcClass.getDeclaredField(srcName);
        }
    };

    private static final Dispatcher mapNotNullSrcfieldsNotNull = (srcClass, srcfields, map, field) -> {
        if (srcfields.contains(field) || srcfields.contains(map.get(field))) {
            String srcName = map.get(field);
            if (null == srcName) {
                return srcClass.getDeclaredField(field);
            } else {
                return srcClass.getDeclaredField(srcName);
            }
        }
        return null;
    };


//    private interface Dispatcher {
//        Field apply(FieldAccess fieldAccess, Set<String> srcfields, Map<String, String> map, String field) throws NoSuchFieldException;
//    }
//
//    private static final Dispatcher mapNullSrcfieldsNull = new Dispatcher() {
//        @Override
//        public Field apply(FieldAccess fieldAccess, Set<String> srcfields, Map<String, String> map, String field) throws NoSuchFieldException {
//            return fieldAccess.field(field);
//        }
//    };
//
//    private static final Dispatcher mapNullSrcfieldsNotNull = new Dispatcher() {
//        @Override
//        public Field apply(FieldAccess fieldAccess, Set<String> srcfields, Map<String, String> map, String field) throws NoSuchFieldException {
//            if (srcfields.contains(field)) {
//                return fieldAccess.field(field);
//            }
//            return null;
//        }
//    };
//
//    private static final Dispatcher mapNotNullSrcfieldsNull = new Dispatcher() {
//        @Override
//        public Field apply(FieldAccess fieldAccess, Set<String> srcfields, Map<String, String> map, String field) throws NoSuchFieldException {
//            String srcName = map.get(field);
//            if (null == srcName) {
//                return fieldAccess.field(field);
//            } else {
//                return fieldAccess.field(srcName);
//            }
//        }
//    };
//
//    private static final Dispatcher mapNotNullSrcfieldsNotNull = new Dispatcher() {
//        @Override
//        public Field apply(FieldAccess fieldAccess, Set<String> srcfields, Map<String, String> map, String field) throws NoSuchFieldException {
//            if (srcfields.contains(field) || srcfields.contains(map.get(field))) {
//                String srcName = map.get(field);
//                if (null == srcName) {
//                    return fieldAccess.field(field);
//                } else {
//                    return fieldAccess.field(srcName);
//                }
//            }
//            return null;
//        }
//    };

    private static final Dispatcher[] DISPATCHERS = new Dispatcher[]{
            mapNullSrcfieldsNull,
            mapNullSrcfieldsNotNull,
            mapNotNullSrcfieldsNull,
            mapNotNullSrcfieldsNotNull
    };

    public static boolean isInvalid(Query query) {
        if (null == query) { throw new NullPointerException("参数不能为null"); }
        if (null == query.keys || null == query.values) { throw new IllegalArgumentException("query未初始化"); }
        String[] keys = query.keys;
        String[][] values = query.values;
        for (int i = 0; i < values.length; i++) {
            if (null == values[i] || values[i].length < 1) {
                return true;
            }
            for (int j = 0; j < values[i].length; j++) {
                if (null == values[i][j] || "".equals(values[i][j])) {
                    return true;
                }
            }
            boolean result = false;
            switch (keys[i]) {
                case "id": case "phone": result = isStringToIntegerInvalid(values[i][0]); break;
                case "price": case "unitPrice": result = isStringToDoubleInvalid(values[i][0]); break;
            }
            if (result) { return true; }
        }
        return false;
    }

    private static boolean isStringToDoubleInvalid(String value) {
        boolean isNumber = StringUtils.isNumber(value);
        if (!isNumber) { return true; }
        double number = Double.parseDouble(value);
        if (0 > number) { return true; }
        return false;
    }

    private static boolean isStringToIntegerInvalid(String value) {
        boolean isNumber = StringUtils.isNumber(value);
        if (!isNumber) { return true; }
        int number = Integer.parseInt(value);
        if (0 > number) { return true; }
        return false;
    }

    public static boolean isInvalid(Object object, List<String> fields) {
        if (null == object || null == fields) { throw new NullPointerException("参数不能为null"); }
        if (fields.isEmpty()) { throw new IllegalArgumentException("字段列表不能为空列表"); }
        try {
            Class<?> clazz = object.getClass();
            MethodAccess methodAccess = MethodAccess.get(clazz);
            for (String f : fields) {
                Field field = clazz.getDeclaredField(f);
                String simpleName = field.getType().getSimpleName();
                String methodName = GET_PREFIX + f.substring(0, 1).toUpperCase() + f.substring(1);
                boolean result = false;
                switch (simpleName) {
                    case "String":
                        result = isStringInvalid((String) methodAccess.invoke(object, methodName));
                        break;
                    case "Integer":
                        result = isIntegerInvalid((Integer) methodAccess.invoke(object, methodName));
                        break;
                    case "Double":
                        result = isDoubleInvalid((Double) methodAccess.invoke(object, methodName));
                        break;
                    default:
                        throw new UnsupportedOperationException("暂不支持" + simpleName + "类型的检测");
                }
                if (result) {
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean isDoubleInvalid(Double invoke) {
        return null == invoke || 0 > invoke;
    }

    private static boolean isStringInvalid(String invoke) {
        return null == invoke || "".equals(invoke);
    }

    private static boolean isIntegerInvalid(Integer invoke) {
        return null == invoke || 0 > invoke;
    }
}
